﻿//=============================================================================
// Project  : NIPO Component Framework
// File    : LocalAssemblyResolver.cs
// Author  : Bernhard Dieber (Bernhard.Dieber@uni-klu.ac.at)
// Copyright 2010 by Bernhard Dieber
// This code is published under the Microsoft Public License (Ms-PL).  A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://NIPO.CodePlex.com.   This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;

namespace NIPO.Controller
{
    /// <summary>
    /// This class is used to resolve assemblis that plugins may depend upon. Search directories may be added to the singleton instance.
    /// </summary>
    internal class LocalAssemblyResolver
    {
        private readonly List<string> _searchPaths;
        private readonly ResolveEventHandler _handler;
        private static readonly LocalAssemblyResolver AssemblyResolverInstance = new LocalAssemblyResolver();
       
        #region Singleton Pattern
        private static LocalAssemblyResolver Instance
        {
            get
            {
                lock (AssemblyResolverInstance)
                {
                    return AssemblyResolverInstance;
                }
            }
        }

        public List<string> SearchPaths
        {
            get { return _searchPaths; }
        }

        public static ReadOnlyCollection<string> GetSearchPaths()
        {
            return AssemblyResolverInstance.SearchPaths.AsReadOnly();
        }

        internal static void AddSearchPath(string path)
        {
            Instance.AddPath(path);
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalAssemblyResolver"/> class.
        /// </summary>
        private LocalAssemblyResolver()
        {
            _searchPaths = new List<string>();
            if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "Shared")))
            {
                SearchPaths.Add(Path.Combine(Directory.GetCurrentDirectory(), "Shared"));
            }
            if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "PlugIns")))
            {
                DirectoryInfo[] subdirs = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "PlugIns")).GetDirectories("*", SearchOption.AllDirectories);
                foreach (DirectoryInfo dir in subdirs)
                {
                    if (!this.SearchPaths.Contains(dir.FullName))
                        this.SearchPaths.Add(dir.FullName);
                }
            }
            _handler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            AppDomain.CurrentDomain.AssemblyResolve += _handler;
            AppDomain.CurrentDomain.TypeResolve += new ResolveEventHandler(CurrentDomain_TypeResolve);
        }

        /// <summary>
        /// Handles the TypeResolve event of the CurrentDomain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
        /// <returns><c>null</c></returns>
        Assembly CurrentDomain_TypeResolve(object sender, ResolveEventArgs args)
        {
            //Console.WriteLine("Type Resolve for {0}", args.Name);
            return null;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="LocalAssemblyResolver"/> is reclaimed by garbage collection.
        /// </summary>
        ~LocalAssemblyResolver()
        {
            //Console.WriteLine("Terminateing AssemblyResolver for {0}", _searchPaths);
            AppDomain.CurrentDomain.AssemblyResolve -= _handler;
        }

        /// <summary>
        /// Adds a new search path.
        /// </summary>
        /// <param name="path">The path.</param>
        internal void AddPath(string path)
        {
            //TODO maybe search for assemblies contained in search path in background. would speed up later assembly resolving
            if (!SearchPaths.Contains(path))
            {
                SearchPaths.Add(path);
            }
        }

        /// <summary>
        /// Handles the AssemblyResolve event of the CurrentDomain control. The first suitable assembly is chosen.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
        /// <returns></returns>
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            /*
             * Policy is to take the first assembly that fits
             */
            AssemblyName aname = new AssemblyName(args.Name);
            foreach (string searchPath in SearchPaths)
            {
                string filepath1 = Path.Combine(searchPath, aname.Name + ".dll");
                string filepath2 = Path.Combine(searchPath, aname.Name + ".exe");
                if (File.Exists(filepath1))
                {
                    Assembly retVal = Assembly.LoadFile(filepath1);
                    return retVal;
                }
                if (File.Exists(filepath2))
                    return Assembly.LoadFile(filepath2);
            }
            //TODO Add manual resolve here
            return null;
        }
    }
}